// parser 相关

// 定义状态机的状态
const State = {
  initial: 1, // 初始状态
  tagOpen: 2, // 标签开始状态
  tagName: 3, // 标签名称状态
  text: 4, // 文本状态
  tagEnd: 5, // 结束标签状态
  tagEndName: 6, // 结束标签名称状态
}

function isAlpha (char) {
  return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z');
}

// 接收模板字符串作为参数，并将模板切割为 Token 返回

function tokenize (str) {
  // 状态机当前的状态：初始状态
  let currentState = State.initial;

  // 用于缓存 字符
  const chars = []

  // 用于缓存 token 类型和值 
  const tokens = []

  while (str) {
    const char = str[0];
    switch (currentState) {
      case State.initial:
        if (char === '<') {
          currentState = State.tagOpen;
          str = str.slice(1);
        } else if (isAlpha(char)) {
          currentState = State.text;
          chars.push(char);
          str = str.slice(1);
        }
        break;

      case State.tagOpen:
        if (isAlpha(char)) {
          currentState = State.tagName;
          chars.push(char);
          str = str.slice(1);
        } else if (char === '/') {
          currentState = State.tagEnd;
          str = str.slice(1);
        }
        break;

      case State.tagName:
        if (char === '>') {
          currentState = State.initial;
          // 创建标签Token 存入Tokens
          tokens.push({
            type: 'tag',
            name: chars.join(''),
          });
          // 清空 chars
          chars.length = 0;
          str = str.slice(1);
        } else if (isAlpha(char)) {
          chars.push(char);
          str = str.slice(1);
        }
        break;

      case State.text:
        if (isAlpha(char)) {
          // 遇到字母状态不改变，
          chars.push(char);
          str = str.slice(1);
        } else if (char === '<') {
          currentState = State.tagOpen;

          tokens.push({
            type: 'text',
            content: chars.join(''),
          });
          chars.length = 0;
          str = str.slice(1);
        }
        break;

      case State.tagEnd:
        if (isAlpha(char)) {
          currentState = State.tagEndName;
          chars.push(char);
          str = str.slice(1);
        }
        break;

      case State.tagEndName:
        if (isAlpha(char)) {
          chars.push(char);
          str = str.slice(1);
        } else if (char === '>') {
          currentState = State.initial;
          tokens.push({
            type: 'tagEnd',
            name: chars.join(''),
          });
          chars.length = 0;
          str = str.slice(1);
        }
        break;
    }
  }
  return tokens;
}

// Tokens 类型
const TAG_TYPE = {
  tag: 'tag',
  text: 'text',
  tagEnd: 'tagEnd'
}



// AST 抽象语法树转化
function parse (str) {
  // 对模板进行标记化，获取tokens
  const tokens = tokenize(str)

  // 创建根节点
  const Root = {
    type: 'Root',
    children: [],
  }

  // 创建elementStack栈 初始化 只有 根节点
  const elementStack = [Root]

  while (tokens.length) {
    // 获取栈顶元素 作为 父节点 parent 
    const parent = elementStack[elementStack.length - 1]

    const t = tokens[0]

    switch (t.type) {
      case TAG_TYPE.tag:
        const element = {
          type: 'Element',
          tag: t.name,
          children: [],
        }
        // 添加到父节点的children中
        parent.children.push(element)
        // 压栈
        elementStack.push(element)
        break;

      case TAG_TYPE.text:
        const textNode = {
          type: 'Text',
          content: t.content,
        }
        parent.children.push(textNode)
        break;

      case TAG_TYPE.tagEnd:
        // 结束 则把 栈顶元素弹出
        elementStack.pop()
        break;
    }

    // 消费掉已经遍历过的token
    tokens.shift()

  }

  return Root
}

const templateStr = '<div><p>Vue</p><p>Template</p></div>'

const AST = parse(templateStr)
// console.log('[90m [ AST ]-189 [0m', JSON.stringify(AST, null, 2))




// dump 函数 用于打印AST

function dump (node, indent = 0) {
  // 节点类型
  const type = node.type

  const desc = type === 'Root' ? '' : (
    type === 'Element' ? node.tag : node.content
  )

  console.log('[38;2;0;255;0m [  ]-204 [0m', `${'-'.repeat(indent)}${type}:${desc}`)

  if (node.children) {
    node.children.forEach(child => {
      dump(child, indent + 2)
    })
  }
}


// 创建节点 辅助函数 START
function createStringLiteral (value) {
  return {
    type: 'StringLiteral',
    value
  }
}

function createIdentifier (name) {
  return {
    type: 'Identifier',
    name
  }
}

function createArrayExpression (elements) {
  return {
    type: 'ArrayExpression',
    elements
  }
}

function createCallExpression (callee, arguments) {
  return {
    type: 'CallExpression',
    callee: createIdentifier(callee),
    arguments
  }
}


// 创建节点 辅助函数 END



// 解耦写法 START
function traverseNode (ast, context) {
  // ast 本身就是 根节点
  // const currentNode = ast

  // 设置当前转换节点
  context.currentNode = ast

  // 记录退出阶段的回调函数数组 栈
  const exitFns = []


  // transforms 是一个数组 里面装着各种转换的函数
  const transforms = context.nodeTransforms
  for (let i = 0; i < transforms.length; i++) {
    const transform = transforms[i]

    //调用转换函数 需返回一个回调
    const onExit = transform(context.currentNode, context)
    if (onExit) {
      exitFns.push(onExit)
    }

    // 如果当前节点被删除了，则结束后续操作
    if (!context.currentNode) {
      return;
    }

  }



  // 递归处理每一个子节点
  const children = context.currentNode.children
  if (children) {
    // children.forEach(child => {
    //     traverseNode(child, context)
    // })

    for (let i = 0; i < children.length; i++) {
      // 递归前将当前转换节点作为 父节点 且记录 子节点索引
      context.parent = context.currentNode;
      context.childIndex = i;

      // 透传context
      traverseNode(children[i], context)
    }
  }


  // 倒叙 执行退出阶段的回调函数 
  // 栈， 先进后出

  let i = exitFns.length
  while (i--) {
    exitFns[i]()
  }
}

function transform (ast) {
  const context = {
    // 增加currentNode 存储当前转换的节点
    currentNode: null,
    //记录转换节点在父节点的索引
    childIndex: 0,
    // 存储转换节点的父节点
    parent: null,

    replaceNode (node) {
      // 找到当前节点在父元素的children中的位置  然后替换
      context.parent.children[context.childIndex] = node;

      // 替换完后 要把当前节点设置为新节点
      context.currentNode = node;
    },

    removeNode () {
      if (context.parent) {
        // 通过splice方法去删除当前节点
        context.parent.children.splice(context.childIndex, 1);
        // 删除完当前节点后 要把当前节点设置为null
        context.currentNode = null;
      }
    },


    nodeTransforms: [
      transformElement,
      transformText
    ]
  }

  traverseNode(ast, context)


  console.log('',)
  console.log('',)
  console.log('',)

  console.log('[38;5;196m [ transform 解耦写法 START]-269 [0m',)
  console.log('[38;5;196m [ transform 解耦写法 END]-269 [0m', dump(ast))
}

// 处理 元素
function transformElement (node, context) {

  if (node.type === 'Element' && node.tag === 'p') {
    node.tag = 'h2'
  }

  // 需将转换代码 卸载退出阶段的回调中 确保所该标签的所有子节点全部处理完毕
  return () => {
    // 不是元素节点就啥也不做
    if (node.type !== 'Element') {
      return;
    }

    // 1.创建h 函数调用语句
    // h 函数调用的第一个参数是标签名称，因此我们以 node.tag 来创建一个字符串字面量节点 
    // 作为第一个参数 
    const callExp = createCallExpression('h',[
      createStringLiteral(node.tag)
    ])

    //2. 处理h 函数调用的参数
    node.children.length ===1
    ?callExp.arguments.push(node.children[0].jsNode)
    :callExp.arguments.push(
      // **** 因为是在退出回调里面 进行转换 所以 已确保child.jsNode 都已经处理完毕
      createArrayExpression(node.children.map(child=>child.jsNode))
    ) 

    // 3.当前标签节点对应的js AST节点 添加到jsNode上
    node.jsNode = callExp

  }
}

// 处理文本内容
function transformText (node, context) {

  // 如果不是文本节点 啥也不做
  if (node.type !== 'Text') {
    return;
  }

  console.log('[38;5;220m [  ]-365 [0m', 'transformText Enter');

  node.jsNode = createStringLiteral(node.content)

  return () => {

    console.log('[38;5;208m [  ]-371 [0m', 'transformText Exit');

  }
}

// 转换Root 根节点
function  transformRoot (node) {

//在退出阶段的回调中 编辑逻辑  ！！！确保 子节点已全部处理完毕
  return () => {
    if(node.type !=='Root'){
      return ;
    }

    // 暂时不考虑多根节点情况， 已单根节点情况处理
    const vNodeJSAST = node.children[0].jsNode

    // 创建render函数 的声明语句节点，  将vNodeJSAST作为render函数的返回值
    node.jsNode = {
      type: 'FunctionDecl',
      id:createIdentifier('render'),
      params:[],
      body:[
        {
          type: 'ReturnStatement',
          return: vNodeJSAST
        }
      ]

    }

  }
}




transform(AST)

// 解耦写法  END